﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DateRange.cs" company="1">
//   Golovkov Alex
// </copyright>
// <summary>
//   Defines the DateRange type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;

namespace ContractManager.UserControls.Controls
{
    /// <summary>
    /// The date range.
    /// </summary>
    public class DateRange : IEquatable<DateRange>
    {
        #region Constants and Fields

        /// <summary>
        /// The end date.
        /// </summary>
        private DateTime? endDate;

        /// <summary>
        /// The start date.
        /// </summary>
        private DateTime? startDate;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DateRange"/> class.    Default constructor. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        public DateRange()
            : this(new DateTime?(), new DateTime?())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DateRange"/> class. 
        /// Default constructor. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <param name="startDate">
        /// The start date. 
        /// </param>
        /// <param name="endDate">
        /// The end date. 
        /// </param>
        public DateRange(DateTime? startDate, DateTime? endDate)
        {
            AssertStartDateFollowsEndDate(startDate, endDate);
            this.startDate = startDate;
            this.endDate = endDate;
        }

        #endregion

        #region Properties

        /// <summary>   Gets or sets the date of the end. </summary>
        /// <value> The date of the end. </value>
        public DateTime? EndDate
        {
            get
            {
                return this.endDate;
            }

            set
            {
                AssertStartDateFollowsEndDate(this.startDate, value);
                this.endDate = value;
            }
        }

        /// <summary>   Gets or sets the date of the start. </summary>
        /// <value> The date of the start. </value>
        public DateTime? StartDate
        {
            get
            {
                return this.startDate;
            }

            set
            {
                AssertStartDateFollowsEndDate(value, this.endDate);
                this.startDate = value;
            }
        }

        /// <summary>   Gets the time span. </summary>
        /// <value> The time span. </value>
        public TimeSpan? TimeSpan
        {
            get { return this.endDate - this.startDate; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets an intersection. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        /// Thrown when the requested operation is
        ///                                                 invalid. 
        /// </exception>
        /// <param name="other">
        /// The other. 
        /// </param>
        /// <returns>
        /// The intersection. 
        /// </returns>
        public DateRange GetIntersection(DateRange other)
        {
            if (!this.Intersects(other))
            {
                throw new InvalidOperationException("DateRanges do not intersect");
            }

            return new DateRange(this.GetLaterStartDate(other.StartDate), this.GetEarlierEndDate(other.EndDate));
        }

        /// <summary>
        /// Check intersects. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <param name="other">
        /// The other. 
        /// </param>
        /// <returns>
        /// true if it succeeds, false if it fails. 
        /// </returns>
        public bool Intersects(DateRange other)
        {
            if ((this.startDate.HasValue && other.EndDate.HasValue &&
                 other.EndDate.Value < this.startDate.Value) ||
                (this.endDate.HasValue && other.StartDate.HasValue &&
                 other.StartDate.Value > this.endDate.Value) ||
                (other.StartDate.HasValue && this.endDate.HasValue &&
                 this.endDate.Value < other.StartDate.Value) ||
                (other.EndDate.HasValue && this.startDate.HasValue &&
                 this.startDate.Value > other.EndDate.Value))
            {
                return false;
            }

            return true;
        }

        #endregion

        #region Implemented Interfaces

        #region IEquatable<DateRange>

        /// <summary>
        /// Tests if this DateRange is considered equal to another. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <param name="other">
        /// The date range to compare to this object. 
        /// </param>
        /// <returns>
        /// true if the objects are considered equal, false if they are not. 
        /// </returns>
        public bool Equals(DateRange other)
        {
            if (ReferenceEquals(other, null))
            {
                return false;
            }

            return (this.startDate == other.StartDate) && (this.endDate == other.EndDate);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Assert start date follows end date. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        /// Thrown when the requested operation is
        ///                                                 invalid. 
        /// </exception>
        /// <param name="inStartDate">
        /// The start date. 
        /// </param>
        /// <param name="inEndDate">
        /// The end date. 
        /// </param>
        private static void AssertStartDateFollowsEndDate(DateTime? inStartDate, DateTime? inEndDate)
        {
            if ((inStartDate.HasValue && inEndDate.HasValue) &&
                (inEndDate.Value < inStartDate.Value))
            {
                throw new InvalidOperationException("Start Date must be less than or equal to End Date");
            }
        }

        /// <summary>
        /// Gets an earlier end date. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <param name="other">
        /// The other. 
        /// </param>
        /// <returns>
        /// The earlier end date. 
        /// </returns>
        private DateTime? GetEarlierEndDate(DateTime? other)
        {
            // !endDate.HasValue == +infinity, not negative infinity
            // as is the case with !startDate.HasValue
            if (Nullable.Compare(this.endDate, other) == 0)
            {
                return other;
            }

            if (this.endDate.HasValue && !other.HasValue)
            {
                return this.endDate;
            }

            if (!this.endDate.HasValue && other.HasValue)
            {
                return other;
            }

            return (Nullable.Compare(this.endDate, other) >= 0) ? other : this.endDate;
        }

        /// <summary>
        /// Gets a later start date. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 21.09.2010. 
        /// </remarks>
        /// <param name="other">
        /// The other. 
        /// </param>
        /// <returns>
        /// The later start date. 
        /// </returns>
        private DateTime? GetLaterStartDate(DateTime? other)
        {
            return Nullable.Compare(this.startDate, other) >= 0 ? this.startDate : other;
        }

        #endregion
    }
}